Skip to content

feat(widget): add limit order mode#793

Draft
effie-ms wants to merge 23 commits into
mainfrom
feat/emb-322-limit-order-mode
Draft

feat(widget): add limit order mode#793
effie-ms wants to merge 23 commits into
mainfrom
feat/emb-322-limit-order-mode

Conversation

@effie-ms

@effie-ms effie-ms commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Which Linear task is linked to this PR?

https://linear.app/lifi-linear/issue/EMB-322

Why was it implemented this way?

Implement limit order mode as a dedicated header tab.

Extended API endpoints: lifinance/sdk#410

Visual showcase (Screenshots or Videos)

TODO: attach a screen recording of the limit price card, presets, invert toggle, and expiry/partial-fill settings.

Checklist before requesting a review

  • I have performed a self-review and testing of my code.
  • This pull request is focused and addresses a single problem.
  • If this PR modifies the Widget API or adds new features that require documentation, I have updated the documentation in the public-docs repository.

@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d0c4b6b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
@lifi/widget Minor
connectkit Patch
deposit-flow Patch
dynamic Patch
nextjs Patch
nextjs15 Patch
nft-checkout Patch
privy-ethers-example Patch
privy Patch
rainbowkit Patch
reown Patch
svelte Patch
tanstack-router-example Patch
vite-project Patch
vue Patch
zustand-widget-config Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@effie-ms effie-ms marked this pull request as draft June 16, 2026 10:53
@effie-ms effie-ms self-assigned this Jun 16, 2026
@effie-ms effie-ms force-pushed the feat/advanced-mode-settings branch 2 times, most recently from 6f66b1a to 14cf8cf Compare June 22, 2026 15:28
@effie-ms effie-ms force-pushed the feat/emb-322-limit-order-mode branch from 58c0a26 to 4b18107 Compare June 24, 2026 10:37
@effie-ms effie-ms changed the base branch from feat/advanced-mode-settings to main June 24, 2026 10:37
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

E2E Examples — all passed

All examples passed in the latest run.

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

❌ E2E Dev Smoke — failed

Check Result
Dev server start (pnpm dev) ✅ started
Smoke tests ❌ failed

1 passed · 3 failed · 0 skipped · 73s

  • clicking the Settings icon opens the Settings view
  • token route setup — From and To tokens selected via UI
  • widget container is displayed with Exchange heading

View run — the full HTML report is attached as the playwright-report-dev-smoke artifact.

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

E2E Playground results

failed  19 failed
passed  139 passed

Details

stats  158 tests across 10 suites
duration  4 minutes, 52 seconds
commit  d0c4b6b

Failed tests

preview › playground/settings.developer-controls.spec.ts › Playground settings — Developer controls (Loading preview) › restores the live widget when toggled off
preview › playground/settings.mode-variant.spec.ts › Playground settings — Mode × Variant: Swap or Bridge + Compact › clicking From navigates inline — chain sidebar does not open
preview › playground/settings.mode-variant.spec.ts › Playground settings — Mode × Variant: Refuel + Compact › Refuel mode UI is correct in Compact layout
preview › playground/settings.mode-variant.spec.ts › Playground settings — Mode × Variant: Refuel + Compact › clicking From navigates inline — chain sidebar does not open
preview › playground/settings.mode-variant.spec.ts › Playground settings — Mode × Variant: Bridge + Drawer › Bridge mode UI is correct inside the Drawer
preview › playground/settings.mode-variant.spec.ts › Playground settings — Mode × Variant: Bridge + Drawer › clicking From navigates inline — chain sidebar does not open
preview › playground/settings.mode.spec.ts › Playground settings — Mode › Exchange mode is selected by default
preview › playground/settings.mode.spec.ts › Playground settings — Mode › selects Swap mode
preview › playground/settings.mode.spec.ts › Playground settings — Mode › selects Bridge mode
preview › playground/settings.mode.spec.ts › Playground settings — Mode › selects Refuel mode
preview › playground/settings.mode.spec.ts › Playground settings — Mode › Swap mode shows the Swap tab and no Bridge tab
preview › playground/settings.mode.spec.ts › Playground settings — Mode › Bridge mode shows the Bridge tab and no Swap tab
preview › playground/settings.mode.spec.ts › Playground settings — Mode › resets mode to default
preview › playground/settings.persistence.spec.ts › Playground settings — Persistence after reload › Mode: Bridge mode survives page reload
preview › playground/settings.persistence.spec.ts › Playground settings — Persistence after reload › Variant: Compact survives page reload
preview › playground/settings.variant.spec.ts › Playground settings — Variant (Compact) › clicking From navigates inside the widget without opening the chain sidebar
preview › playground/settings.variant.spec.ts › Playground settings — Variant (Drawer) › clicking From navigates inside the widget without opening the chain sidebar
preview › playground/settings.variant.spec.ts › Playground settings — Variant (Wide) › clicking To opens the chain sidebar
preview › playground/settings.wallet-management.spec.ts › Playground settings — Wallet management (External) › navigation header shows a Close button in External mode (Drawer variant, no Force internal)

📥 Download full HTML report (open the run → Artifacts → playwright-report)

@lifi-qa-agent

lifi-qa-agent Bot commented Jun 30, 2026

Copy link
Copy Markdown

🔍 QA Review — EMB-322

🔗 Linear Ticket · Pull Request #793

⚠️ Re-review — 2 new commits pushed after initial CHANGES_REQUESTED (2026-06-30): 8ba07dce (review feedback fixes) and 326d11e1 (form merge refinement). Developer responses reviewed for all 12 previously flagged items.

🧠 What this ticket does

Adds a new "Limit" mode to the LI.FI widget — a bespoke feature for Jumper Pro's right-side order entry panel. The implementation introduces a mode toggle via _navigationTabs, a limit price card with base/quote flip and quick-set presets, an order expiry selector, a partial-fill toggle, and linked field derivation (limitPrice = receiveAmount / sendAmount). The execution flow reuses the existing widget infrastructure (routes → signing → relay → status polling), adding validUntil and partiallyFillable params. This is an InternalWidgetMode feature not surfaced to B2B integrators.

Verdict: Needs Work — one hard merge blocker remains open: _navigationTabs: ['swap-advanced', 'bridge-advanced', 'limit'] is still uncommented in defaultWidgetConfig.ts, breaking 22 CI E2E tests. All other previously flagged items are resolved or accepted with justification. Two pre-merge hygiene items (public-docs checklist, visual showcase) are pending completion.


📋 Previously Flagged Items — Resolution Status

# Severity Item Status
1 🔴 Critical CI failures — _navigationTabs uncommented in playground config ❌ Still present
2 🟠 High mergeDefaultFormValues boolean-false corruption ✅ Resolved
3 🟠 High validUntil anchored to route-fetch time, not signing time ✅ Accepted (justified)
4 🟠 High No limit-order-specific success summary (AC 13) ✅ Accepted (justified)
5 🟡 Medium ReviewButton shows wrong label in limit mode ✅ Resolved
6 🟡 Medium useLimitMarketRate falsy guard undocumented ✅ Resolved
7 🟡 Medium Preset percentages diverge from ticket spec ✅ Accepted (justified)
8 🟡 Medium PR checklist "public-docs" unchecked ⚠️ Pending
9 🟡 Medium isString(str: any) untyped parameter ✅ Resolved
10 🟢 Low aria-disabled on custom chip has no keyboard effect ✅ Resolved
11 🟢 Low No unit tests for new hooks/components ✅ Accepted (justified)
12 🟢 Low Visual showcase listed as TODO in PR body ⚠️ Pending

⚠️ Issues Found (1 open)

# Severity Type Issue
1 🔴 Critical Code CI failures — _navigationTabs still uncommented in defaultWidgetConfig.ts

🔴 [Critical] _navigationTabs still uncommented — CI is failing

packages/widget-playground/src/defaultWidgetConfig.ts HEAD (commit 326d11e1) still has:

_navigationTabs: ['swap-advanced', 'bridge-advanced', 'limit'],

Neither post-review commit addressed this. The CI run against 8ba07dce (the most recent CI result) shows 19 Playground E2E failures and 3 Dev Smoke failures — all caused by the playground defaulting to a tab-bar widget instead of the plain Exchange view.

Developer's stated intent: "kept enabled on purpose for now to exercise the tab bar during testing; will restore before merge." That commitment is not yet fulfilled.

Fix: // _navigationTabs: ['swap-advanced', 'bridge-advanced', 'limit']. The limit tab is exercisable via the playground's mode panel without this being the global default.


✅ Resolved Items — Verification Notes

Item 2 — mergeDefaultFormValues fix (verified correct)

Final guard in createFormStore.ts:

value:
  userValues[key]?.value !== undefined && userValues[key]?.value !== ''
    ? userValues[key]?.value
    : defaultValues[key]?.value,
  • partiallyFillable = falsefalse !== undefined && false !== '' = true → user value preserved ✅
  • numeric 0 → likewise preserved ✅
  • empty string '' → falls to default (intentional — commit 326d11e1 preserves text-field clearing semantics) ✅

The second commit correctly refines the first fix. No regression introduced.

Item 10 — aria-disabled keyboard accessibility (fully resolved)

EditablePriceChip is a styled Box (<div>) with no tabIndex and no role — not in the keyboard tab order. The inner PriceChipInput is a native <input> with disabled={disabled} — natively removed from tab order when disabled. CSS pointerEvents: none via '&[aria-disabled="true"]' blocks mouse interaction. The onClick guard is defence-in-depth. Fix is architecturally sound.

Items 3, 4, 7, 11 — Accepted with justification

  • Item 3 (validUntil): SDK @lifi/sdk@4.1.0 processes validUntil backend-side at route resolution; 60s refetch keeps drift negligible on a 1-day order. Justified.
  • Item 4 (success summary): No limit-specific success screen designed for this iteration. AC 13 remains open on ticket as a documented deferral. Justified.
  • Item 7 (preset percentages): [0, 1, 5, 10] confirmed as current design. Ticket text is outdated. Justified.
  • Item 11 (unit tests for hooks): No DOM/React test environment in widget package. Derivation math covered by limitOrder.test.ts. Justified.

⚠️ Pre-merge Hygiene (2 items — no new QA cycle required)

  • Item 8 — Public docs: WidgetMode = 'limit' and WidgetEvent.NavigationTabChanged are new exported public types. PR checklist item unchecked. Complete before merge.
  • Item 12 — Visual showcase: PR body still contains _TODO: attach a screen recording.... Complete before merge.

🆕 New Issues Introduced (0)

No new defects in the post-review commits. Changes are narrowly scoped to the flagged items.


🧪 Test Coverage

Layer Score Files reviewed
Unit (Vitest) Partial packages/widget/src/utils/limitOrder.test.ts (pure utils covered), no hook/component tests
E2e (Playwright) N/A CI failures caused by Issue #1 — will clear once _navigationTabs is commented out

Remaining unit gap (Low, carried from prior review):

  • applyPriceOffset(price, 0) (Market preset) — not tested. Low risk, but the branch is exercised by every Market button click.

🔗 Downstream Impact

Blocks: EMB-323 (Integrate limit order execution flow with existing Jumper API)

  • NavigationTabChanged event ✅ delivered
  • selectedRouteId store field ✅ delivered
  • EIP-712 signing path ✅ reused
  • Limit-order success summary ❌ deferred (confirm EMB-323 tracks this if needed)

QA Agent — 2026-07-01 (re-review of initial 2026-06-30 review)

@lifi-qa-agent lifi-qa-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on 12 items — each requires either a code fix or an explicit acceptance comment with justification before this review is considered complete.

# Severity Type Issue / File
1 🔴 Critical Code CI failures caused by uncommented playground _navigationTabs — 22 tests broken
2 🟠 High Code mergeDefaultFormValues boolean-false corruption — partiallyFillable=false reverts to true on config update
3 🟠 High Code validUntil anchored to route-fetch time, not signing time
4 🟠 High AC deviation No limit-order-specific success summary (AC 13: "pair, price, validity, protocol" unimplemented)
5 🟡 Medium Code ReviewButton shows "Swap Review"/"Bridge Review" label in limit mode
6 🟡 Medium Code useLimitMarketRate falsy guard silently suppresses market rate when priceUSD is "0"
7 🟡 Medium AC deviation Preset percentages diverge from ticket spec (+1%/+5%/+10% vs described +2%/+5%)
8 🟡 Medium Documentation PR template checklist "updated public-docs" unchecked
9 🟡 Medium Code isString(str: any) in createFormStore.ts — untyped parameter
10 🟢 Low FE8 aria-disabled on custom chip has no keyboard effect — LimitPricePresets.tsx
11 🟢 Low Test gap packages/widget/src/hooks/useLinkedLimitFields.test.ts (new)
12 🟢 Low Documentation Visual showcase in PR description listed as TODO

1. [Critical] CI failures — comment out _navigationTabs in defaultWidgetConfig.ts
packages/widget-playground/src/defaultWidgetConfig.ts has _navigationTabs: ['swap-advanced', 'bridge-advanced', 'limit'] uncommented, changing the playground default from "Exchange" to tab-bar. This breaks 22 tests across 7 suites. Fix: comment the line back out. The limit tab is exercisable via the playground mode panel.

2. [High] mergeDefaultFormValues boolean-false corruption
createFormStore.ts: false || Number.isFinite(false) evaluates to false, so partiallyFillable=false is treated as "no user value" and reverts to the default (true) on any setDefaultValues call triggered by a config update. Replace with userValues[key]?.value !== undefined ? userValues[key]?.value : defaultValues[key]?.value.

3. [High] validUntil computed at route-fetch time
useRoutes.ts: Math.floor(Date.now() / 1000) + validUntilDuration is evaluated when the route query runs and re-anchored on each 60s auto-refetch. Move this to signing time, or confirm with the API team whether the routes endpoint uses validUntil at all.

4. [High] No limit-order success summary
AC 13 specifies "pair, price, validity, protocol" in the success state. The generic swap/bridge screen is shown instead. Add a LimitOrderSuccessContent component, or document explicitly in the ticket that this is deferred to EMB-323.

5. [Medium] ReviewButton label in limit mode
Add case 'limit': in getButtonText() and a corresponding button.limitReview i18n key.

6. [Medium] useLimitMarketRate falsy guard
Add a comment confirming !fromPrice is intentional when priceUSD = "0" (LiFi API convention), or use Number(priceUSD) > 0 for explicit clarity.

7. [Medium] Preset percentages
PRESETS = [0, 1, 5, 10] vs ticket "+2%, +5%". Confirm with product/design whether the implementation values are agreed.

8. [Medium] Public-docs checklist
Confirm whether WidgetEvent.NavigationTabChanged and WidgetMode = 'limit' require public documentation.

9. [Medium] isString(str: any) type
Replace with const isString = (str: unknown): str is string => typeof str === 'string' || str instanceof String.

10. [Low] aria-disabled keyboard accessibility
Add tabIndex={disabled ? -1 : 0} to EditablePriceChip in LimitPricePresets.tsx.

11. [Low] Missing unit test — useLinkedLimitFields
Add packages/widget/src/hooks/useLinkedLimitFields.test.ts covering: setSendAmount recomputes receiveAmount, setReceiveAmount updates limitPrice derivation, pair-change reset.

12. [Low] Visual showcase
Complete the TODO in the PR description before merge.

💡 Once you've addressed the items above, re-apply the "Agent Review Request" label to trigger an automated re-review.

@effie-ms

effie-ms commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review — here's the disposition of each item.

Fixed (commit 8ba07dce)

  • Item 2 — mergeDefaultFormValues boolean-false corruption: the merge now preserves any explicit user value via userValues[key]?.value !== undefined ? … : default, so a deliberate partiallyFillable=false (or 0) survives a config-driven setDefaultValues instead of reverting to the default.
  • Item 5 — ReviewButton label in limit mode: added case 'limit' to both getButtonText() switches, using a new button.review ("Review") key.
  • Item 6 — useLimitMarketRate guard: added a clarifying comment. The code already parses with Number(priceUSD), so a "0"/missing price correctly returns undefined (and avoids an Infinity rate) — behaviour was correct, intent is now documented. No logic change.
  • Item 9 — isString(str: any): now (str: unknown): str is string.
  • Item 10 — aria-disabled on the custom chip: the onClick now early-returns when disabled, so aria-disabled actually blocks activation. (Used a handler guard rather than tabIndex — the wrapper is a <div>, not a tab stop, and the inner <input> already handles native disabled for keyboard focus.)

Accepted with justification

  • Item 3 — validUntil anchoring: @lifi/sdk@4.1.0 natively supports validUntil / partiallyFillable (getRoutes resolves them on the backend), so no transport workaround is needed. The value re-anchors on each 60s refetch, which keeps it close to signing time — intended behaviour, not a bug.
  • Item 4 — limit-order success summary: out of design scope — no limit-specific success screen was designed for this iteration. Not implementing here.
  • Item 7 — preset percentages: [0, 1, 5, 10] matches the current design; the ticket's "+2%/+5%" wording is outdated.
  • Item 11 — useLinkedLimitFields unit test: the widget package has no DOM/React test environment (no jsdom / testing-library / renderHook) — all existing tests are pure functions. The hook's derivation math (deriveReceiveAmount / deriveLimitPrice) is already covered by limitOrder.test.ts, so the hook-level test is skipped rather than introducing a full harness for it.

Intentional / handled elsewhere

  • Item 1 — _navigationTabs in the playground: kept enabled on purpose for now to exercise the tab bar during testing; will restore before merge.
  • Item 8 — public docs: to be handled in the public-docs repo.
  • Item 12 — visual showcase: PR description will be updated with a recording.

@lifi-qa-agent lifi-qa-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on 1 remaining blocking item — the other 11 items from the prior review have been resolved or accepted with justification.

# Severity Type Issue / File
1 Critical Code Comment out _navigationTabs in packages/widget-playground/src/defaultWidgetConfig.ts before merge

1. [Critical] _navigationTabs still uncommented — CI broken

packages/widget-playground/src/defaultWidgetConfig.ts has _navigationTabs: ['swap-advanced', 'bridge-advanced', 'limit'] still active on HEAD (326d11e1). This changes the playground default from "Exchange" to a tab-bar layout and breaks 19 Playground E2E tests and 3 Dev Smoke tests.

Fix: restore the comment — // _navigationTabs: ['swap-advanced', 'bridge-advanced', 'limit'].

The developer acknowledged this in their response ("will restore before merge") but neither of the two post-review commits addressed it. All other items from the prior review are resolved or accepted.

Pre-merge hygiene also open (not hard-blockers, but should be completed):

  • Item 8: PR template checklist "updated public-docs" still unchecked — check the box or add a note explaining no docs change is needed.
  • Item 12: PR description still has _TODO: attach a screen recording... — update with the recording as stated.

Once _navigationTabs is commented out and CI passes, re-apply the "Agent Review Request" label to trigger a final automated re-review.

@lifi-qa-agent lifi-qa-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review complete — requesting changes on 1 remaining item. 10 of 12 previously flagged items are resolved or accepted.

# Severity Type Issue / File
1 🔴 Critical Code Comment out _navigationTabs in packages/widget-playground/src/defaultWidgetConfig.ts

1. [Critical] _navigationTabs still uncommented — CI is failing

packages/widget-playground/src/defaultWidgetConfig.ts HEAD (326d11e1) still has _navigationTabs: ['swap-advanced', 'bridge-advanced', 'limit'] uncommented. This causes the playground to default to a tab-bar layout instead of the plain Exchange view, breaking 19 Playground E2E and 3 Dev Smoke tests.

You committed to "restoring before merge" — please comment this line out now:

// _navigationTabs: ['swap-advanced', 'bridge-advanced', 'limit'],

The limit tab is exercisable via the playground mode panel without it being the global default. All other previously flagged items are cleared.

Pre-merge hygiene (no new cycle required — just complete before merging):

  • Item 8: Check off or link a public-docs PR for WidgetMode = 'limit' / WidgetEvent.NavigationTabChanged type additions.
  • Item 12: Attach the screen recording to the PR description.

💡 Once you've addressed the items above, re-apply the "Agent Review Request" label to trigger an automated re-review.

@effie-ms

effie-ms commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Widget flows: navigation tabs, modes & per-tab UI

This doc covers the config surface added on the limit-order branch: _navigationTabs,
the per-tab config layering, the new limit mode, defaultUI.layout, and the
NavigationTabChanged event. It shows how to wire up both a standard (single-flow)
widget and an advanced (tabbed) widget.

⚠️ _navigationTabs, mode: 'limit', and the internal tab keys (default,
private, refuel, swap-advanced, bridge-advanced, limit) are marked
@internal — not part of the stable public API yet. Only swap/bridge tab keys
and the non-limit modes are public.


The two ways to pick a flow

There are two levers, and they don't stack — _navigationTabs wins when both are set.

Lever What it does Renders a tab bar?
mode (+ modeOptions) Picks a single flow for the whole widget Only split mode with { defaultTab }
_navigationTabs Renders a tab bar; the active tab drives the flow Always

Resolution order (getNavigationTabKeys)

  1. If _navigationTabs is set and non-empty → render exactly those tabs.
  2. Else if mode: 'split' and modeOptions.split is an object (not a pinned
    string) → render the implicit ['swap', 'bridge'] tabs.
  3. Else → no tab bar; mode alone drives a single flow.

Standard flows (no tab bar)

Set mode and leave _navigationTabs unset.

// Combined swap + bridge in one form
const config: WidgetConfig = { mode: 'default' }

// Gas / refuel only
const config: WidgetConfig = { mode: 'refuel' }

// Split, pinned to a single side (no tabs — the string pins it)
const config: WidgetConfig = { mode: 'split', modeOptions: { split: 'swap' } }

// Split with a Swap/Bridge tab bar (object form → implicit tabs)
const config: WidgetConfig = {
  mode: 'split',
  modeOptions: { split: { defaultTab: 'swap' } },
}

Available mode values: 'default' | 'split' | 'custom' | 'refuel' | 'limit'
('limit' is internal; 'custom' is driven by modeOptions.custom.type).


Advanced flows (explicit tab bar)

Set _navigationTabs to an ordered list of tab keys. The first entry is the
initially active tab. Each key is a preset that expands into a variant + mode

  • modeOptions + defaultUI + requiredUI bundle.
// The "advanced" preset: two split sides + limit orders
const config: WidgetConfig = {
  _navigationTabs: ['swap-advanced', 'bridge-advanced', 'limit'],
}

// A "simple" tabbed setup
const config: WidgetConfig = {
  _navigationTabs: ['default', 'private', 'refuel'],
}

Tab key reference

Each key resolves to the bundle below. defaultUI: { layout: 'cards' } is applied to
every configured tab, so the tabbed experience uses the card-based form layout.

Key Label variant mode modeOptions requiredUI Notes
default Swap & Bridge wide default Combined form
private Private compact default toAddress: true Forces a recipient address
refuel Gas wide refuel Gas top-up
limit Limit compact limit Limit-order flow
swap-advanced Swap wide split { split: 'swap' } Same-chain focus
bridge-advanced Bridge wide split { split: 'bridge' } Cross-chain focus
swap Swap split { split: 'swap' } Public split key
bridge Bridge split { split: 'bridge' } Public split key

Labels come from i18n (header.*) and are overridable via the widget's translation
config.


Per-tab config layering

While a tab is active, its bundle is layered over the widget-level config, so the
rest of the widget reads tab-driven values transparently
(NavigationTabsStoreProvider). Precedence, per field:

  • mode, variant, modeOptionstab value, falling back to config (tab replaces).
  • defaultUI, requiredUIshallow-merged, tab wins on collisions
    ({ ...config.defaultUI, ...tab.defaultUI }). Fields the tab doesn't set are
    inherited from the config.

Example — a config-level defaultUI survives except where the tab overrides it:

// config.defaultUI = { transactionDetailsExpanded: true }
// active tab (e.g. 'private') sets defaultUI = { layout: 'cards' }
// → effective defaultUI = { transactionDetailsExpanded: true, layout: 'cards' }

Tabs with no defaultUI/requiredUI (the swap/bridge split keys) leave the
config's values untouched.


defaultUI.layout

New defaultUI field controlling the main form layout:

defaultUI?: {
  transactionDetailsExpanded?: boolean
  navigationHeaderTitleNoWrap?: boolean
  layout?: 'default' | 'cards' // @default 'default'
}
  • 'default' — classic chain/token selectors + single amount input.
  • 'cards' — redesigned stacked Send/Receive amount cards (AmountInputCardPair)
    with inline token pills and a swap button. All configured navigation tabs set this.

You can opt a standard (tabless) widget into the card layout directly:

const config: WidgetConfig = { mode: 'default', defaultUI: { layout: 'cards' } }

Reacting to tab changes

A NavigationTabChanged event fires on the first resolved tab and on every switch:

import { WidgetEvent, useWidgetEvents } from '@lifi/widget'

const widgetEvents = useWidgetEvents()
widgetEvents.on(WidgetEvent.NavigationTabChanged, ({ tab, previousTab }) => {
  // tab: NavigationTabKey (now active)
  // previousTab: NavigationTabKey | undefined (undefined on first render)
})

Remember to clean up with .off(WidgetEvent.NavigationTabChanged, handler).


Playground

packages/widget-playground/src/defaultWidgetConfig.ts ships the advanced preset
enabled so you can see it immediately:

_navigationTabs: ['swap-advanced', 'bridge-advanced', 'limit'],

Swap the array (or comment it out and set mode) to try other flows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants